﻿using System;
using System.Web.Services.Protocols;

using Rsdn.Janus.Synchronization;

namespace Rsdn.Janus
{
    public abstract class SimpleSyncTask<TSvc, TRq, TRsp> : IWebSvcSyncTask<TSvc>
        where TSvc : SoapHttpClientProtocol
        where TRq : class
    {
        private readonly Func<string> _displayNameGetter;
        private readonly string _name;

        /// <summary>
        /// Инициализирует экземпляр
        /// </summary>
        /// <param name="name">уникальное в пределах провайдера имя задачи</param>
        /// <param name="displayNameGetter">
        /// Делегат, возвращающий отображаемое имя задачи. Делегат используется,
        /// потому что имя зависит от локали потока, а она может
        /// меняться уже после конструирования.
        /// </param>
        protected SimpleSyncTask(
            string name,
            Func<string> displayNameGetter)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            if (displayNameGetter == null)
                throw new ArgumentNullException("displayNameGetter");

            _name = name;
            _displayNameGetter = displayNameGetter;
        }

        #region IWebSvcSyncTask<TSvc> Members
        /// <summary>
        /// Имя задачи.
        /// </summary>
        public virtual string Name
        {
            get { return _name; }
        }

        /// <summary>
        /// Отображаемое имя задачи.
        /// </summary>
        public string GetDisplayName()
        {
            return _displayNameGetter();
        }

        public abstract bool IsTaskActive();

        /// <summary>
        /// Determines whether is synchronization step must be repeated.
        /// Actual for getting the users.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="svc">The SVC.</param>
        /// <param name="rsp">The RSP.</param>
        /// <returns>
        /// 	<c>true</c> if is synchronization steps must be repeated; otherwise, <c>false</c>.
        /// </returns>
        protected virtual bool IsSyncMustBeRepeated(ISyncContext context, TSvc svc, TRsp rsp)
        // ISyncContext context, JanusAT svc, UserResponse rsp on the sync side
        {
            return false;
        }

        /// <summary>
        /// Выполнить синхронизацию.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="svc">The SVC.</param>
        /// <param name="retries">The retries.</param>
        /// <param name="indicator">The indicator.</param>
        /// <exception cref="ArgumentNullException"><paramref name="context"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="svc"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="indicator"/> is <c>null</c>.</exception>
        public void Sync(ISyncContext context, TSvc svc, int retries, ITaskIndicator indicator)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            if (svc == null)
            {
                throw new ArgumentNullException("svc");
            }

            if (indicator == null)
            {
                throw new ArgumentNullException("indicator");
            }

            indicator.SetTaskState(SyncTaskState.Sync);
            try
            {
                context.CheckState();

                SyncTaskState syncTaskStateAtFinish = SyncTaskState.Succeed;
                string finishText = SyncResources.IndicatorSuccessFinish;
                TRsp rsp;

                do
                {
                    indicator.SetStatusText(SyncResources.IndicatorPrepareRequest);
                    TRq rq = PrepareRequest(context);
                    if (rq == null)
                    {
                        return; // Nothing to do
                    }

                    context.CheckState();

                    indicator.SetStatusText(SyncResources.IndicatorMakeRequest);

                    Func<TRsp> requestMaker = delegate { return MakeRequest(context, svc, rq); };
                    rsp = context.CallWithRetries(rq, GetDisplayName(), retries, requestMaker);

                    context.CheckState();

                    indicator.SetStatusText(SyncResources.IndicatorProcessResponse);
                    try
                    {
                        ProcessResponse(context, rq, rsp);
                    }
                    catch (SyncTroubleException ex)
                    {
                        syncTaskStateAtFinish = SyncTaskState.Failed;
                        finishText = SyncResources.IndicatorNotSuccessFinish;
                    }
                }
                while (IsSyncMustBeRepeated(context, svc, rsp));

                context.CheckState();
                indicator.SetTaskState(syncTaskStateAtFinish);
                indicator.SetStatusText(finishText);
            }
            catch (UserCancelledException)
            {
                indicator.SetTaskState(SyncTaskState.Failed);
                indicator.SetStatusText(SyncResources.IndicatorAborted);
                // Отмену пользователем не трактуем как ошибку и выкидываем наверх
                throw;
            }
            catch (Exception ex)
            {
                indicator.SetTaskState(SyncTaskState.Failed);
                indicator.SetStatusText(ex.Message);
                SyncErrorInfo syncErrorInfo = new SyncErrorInfo(SyncErrorType.CriticalError, GetDisplayName(), ex.ToString());
                context.TryAddSyncError(syncErrorInfo);
            }
        }
        #endregion

        protected abstract TRq PrepareRequest(ISyncContext context);
        protected abstract TRsp MakeRequest(ISyncContext context, TSvc svc, TRq rq);
        protected abstract void ProcessResponse(ISyncContext context, TRq request, TRsp response);
    }
}